package com.wzw.springboot.principle.use.security.infrastructure.filter;

import com.wzw.springboot.principle.use.security.infrastructure.model.SecurityUser;
import com.wzw.springboot.principle.use.security.manage.TokenManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StreamUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

/**
 * @author Wangzhiwen
 */
@Slf4j
// @Component
@RequiredArgsConstructor
public class JwtTokenFilter extends OncePerRequestFilter {

    private final TokenManager tokenManager;

    /**
     * 如果当前过滤器，抛出异常后是否还需要继续执行。
     * 默认都是 true  就是说当此过滤器执行一次后报错了 下一次在执行到这里的时候就不会执行 doFilterInternal 方法，
     * 而是直接去执行 过滤器链 doFilter 方法。也就是说，过滤器链会继续去执行下一个过滤器。
     * <p>
     * 因为执行过程中抛出异常后，request的请求路径就会被改为 /error。而error路径会默认被security过滤器链拦截。
     * 最终会走到 FilterSecurityInterceptor 过滤器中，这个过滤器是认证与授权的核心实现逻辑，所以认证不通过时，就会抛出认证失败的异常。然后走到handler处理器中。
     * <p>
     * <p>
     * 如果规定了 MVC 异常处理器，也就是常说的全局异常处理器。捕获到此类异常，异常过滤链return到DispatcherServlet中时异常就会被解析。直接走异常处理逻辑了，这样就不会在去执行剩下的过滤器链了。
     * 也就是走不到 security 的认证失败处理器里了。
     * 上面能走到 MVC 异常处理器结论是错误的，过滤器链中会吞掉异常
     *
     * @return true or false
     */
/*    @Override
    protected boolean shouldNotFilterErrorDispatch() {
        return false;
    }*/
    @Override
    @SuppressWarnings("all")
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("已执行 JwtTokenFilter ");
        UserTokenRequestWrapper requestWrapper = new UserTokenRequestWrapper(request, response);
        // 不管校验是否通过都放行，后面会有一个处理异常的过滤器。最终认证失败会走到 AuthenticationEntryPoint 的实现类中。
        // 在 Security 过滤器链中不要抛出异常，因为这里异常后过滤器链并不会全部断掉。类似递归逻辑，异常会被 Security 吞掉，走不到全局的异常处理器。
        // 除非走到了Servlet，这里抛出的异常才会被全局异常处理器捕获.
        // 如果抛出了异常会导致 Request 请求体内的请求地址变为 /error , 这样就相当于内部转发请求了。会再次走一遍全部的过滤器，也会导致配置的放行策略，URL 都不会生效了。因为 /error 地址没有被放行。
        filterChain.doFilter(requestWrapper, response);
    }

    public class UserTokenRequestWrapper extends HttpServletRequestWrapper {
        private final byte[] body;
        private Long userId = null;
        private final Map<String, String[]> params;

        public UserTokenRequestWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
            super(request);
            params = request.getParameterMap();
            InputStream data = request.getInputStream();
            body = StreamUtils.copyToByteArray(data);
            // 获取token
            SecurityUser securityUser;
            try {
                securityUser = tokenManager.parse(request);
            } catch (Exception e) {
                return;
            }
            // 提前续签token
            tokenManager.renewal(securityUser);
            // 填充 SecurityContextHolder 已经授权了 super.setAuthenticated(true); 并设置了当前用户的权限列表
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(securityUser, securityUser.getPassword(), securityUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            // 赋值
            this.userId = securityUser.getId();
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
            return new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return false;
                }

                @Override
                public boolean isReady() {
                    return false;
                }

                @Override
                public void setReadListener(ReadListener readListener) {
                }

                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
            };
        }

        @Override
        public String getHeader(String name) {
            if ("UserId".equals(name)) {
                return String.valueOf(this.userId);
            }
            return super.getHeader(name);
        }

        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(getInputStream()));
        }

        @Override
        public Enumeration<String> getHeaderNames() {
            List<String> names = Collections.list(super.getHeaderNames());
            names.addAll(Collections.list(super.getParameterNames()));
            return Collections.enumeration(names);
        }

        @Override
        public Map<String, String[]> getParameterMap() {
            return params;
        }
    }
}
